package compiler;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;

import compiler.error.CCompilingError;
import compiler.error.CExpectedError;
import compiler.error.CExpectedIdentifierError;
import compiler.error.CIntOverflowError;
/**
 *
 * A class to turn an input stream into a stream of tokens.
 * There are a number of methods that define the lexical
 * syntax of tokens.
 * @version 1.10, 08/15/95
 * @author  James Gosling Gosling
 *
 */
class StreamPascalTokenizer
{
    private InputStream input;
    private char buf[];
    private int peekc = ' ';
    private boolean pushedBack;
    private boolean forceLower;
    /** The line number of the last token read */
    private int LINENO = 1;

    private boolean eolIsSignificantP = false;

    private byte ctype[] = new byte[256];
    private static final byte CT_WHITESPACE = 1;
    private static final byte CT_DIGIT = 2;
    private static final byte CT_ALPHA = 4;
    private static final byte CT_QUOTE = 8;
    private static final byte CT_COMMENT = 16;

    /**
     * The type of the last token returned.  It's value will either
     * be one of the following PASCAL.TT_* constants, or a single
     * character.  For example, if '+' is encountered and is
     * not a valid word character, ttype will be '+'
     */
    public int ttype;		//the token type

    /**
     * The Stream value.
     */
    public String sval;		//the string value

    /**
     * The number value.
     */
	public int ival;		//interger number
	Hashtable keyTable;		//the key word table
    /**
     * Creates a stream tokenizer that parses the specified input
     * stream.
     * By default, it recognizes numbers, Strings quoted with
     * single and double quotes, and all the alphabetics.
     * @param I the input stream
     */
    public StreamPascalTokenizer (InputStream I)
	{
		input = I;
		buf = new char[20];
		int i;
		lowerCaseMode(true);
		wordChars('a', 'z');
		wordChars('A', 'Z');
		wordChars('_', '_');
		wordChars(128 + 32, 255);
		whitespaceChars(0, ' ');
		parseNumbers();

		keyTable = new Hashtable();

		keyTable.put("and", new Integer(CPascalCompiler.TT_AND));
		keyTable.put("array", new Integer(CPascalCompiler.TT_ARRAY));
		keyTable.put("begin", new Integer(CPascalCompiler.TT_BEGIN));
		keyTable.put("const", new Integer(CPascalCompiler.TT_CONST));
		keyTable.put("do", new Integer(CPascalCompiler.TT_DO));
		keyTable.put("div", new Integer(CPascalCompiler.TT_DIV));
		keyTable.put("downto", new Integer(CPascalCompiler.TT_DOWNTO));
		keyTable.put("else", new Integer(CPascalCompiler.TT_ELSE));
		keyTable.put("end", new Integer(CPascalCompiler.TT_END));
		keyTable.put("for", new Integer(CPascalCompiler.TT_FOR));
		keyTable.put("if", new Integer(CPascalCompiler.TT_IF));
		keyTable.put("mod", new Integer(CPascalCompiler.TT_MOD));
		keyTable.put("not", new Integer(CPascalCompiler.TT_NOT));
		keyTable.put("of", new Integer(CPascalCompiler.TT_OF));
		keyTable.put("or", new Integer(CPascalCompiler.TT_OR));
		keyTable.put("procedure", new Integer(CPascalCompiler.TT_PROCEDURE));
		keyTable.put("program", new Integer(CPascalCompiler.TT_PROGRAM));
		keyTable.put("record", new Integer(CPascalCompiler.TT_RECORD));
		keyTable.put("then", new Integer(CPascalCompiler.TT_THEN));
		keyTable.put("to", new Integer(CPascalCompiler.TT_TO));
		keyTable.put("type", new Integer(CPascalCompiler.TT_TYPE));
		keyTable.put("var", new Integer(CPascalCompiler.TT_VAR));
		keyTable.put("while", new Integer(CPascalCompiler.TT_WHILE));
		keyTable.put(":=", new Integer(CPascalCompiler.TT_ASSIGN));
		keyTable.put("<=", new Integer(CPascalCompiler.TT_LE));
		keyTable.put(">=", new Integer(CPascalCompiler.TT_GE));
		keyTable.put("<>", new Integer(CPascalCompiler.TT_NE));
		keyTable.put("..", new Integer(CPascalCompiler.TT_DD));

	}

    /**
     * Resets the syntax table so that all characters are special.
     */
    public void resetSyntax()
	{
		for (int i = ctype.length; --i >= 0;)
	    ctype[i] = 0;
    }

    /**
     * Specifies that characters in this range are word characters.
     * @param low the low end of the range
     * @param hi the high end of the range
     */
    public void wordChars(int low, int hi)
	{
		if (low < 0) low = 0;
		if (hi > ctype.length) hi = ctype.length;
		while (low <= hi) ctype[low++] |= CT_ALPHA;
    }

    /**
     * Specifies that characters in this range are whitespace
     * characters.
     * @param low the low end of the range
     * @param hi the high end of the range
     */
    public void whitespaceChars(int low, int hi)
	{
		if (low < 0) low = 0;
		if (hi > ctype.length) hi = ctype.length;
		while (low <= hi) ctype[low++] = CT_WHITESPACE;
    }

    /**
     * Specifies that characters in this range are 'ordinary'.
     * Ordinary characters mean that any significance as words,
     * comments, strings, whitespaces or number characters are removed.
     * When these characters are encountered by the
     * parser, they return a ttype equal to the character.
     * @param low the low end of the range
     * @param hi the high end of the range
     */
    public void ordinaryChars(int low, int hi)
	{
		if (low < 0) low = 0;
		if (hi > ctype.length) hi = ctype.length;
		while (low <= hi) ctype[low++] = 0;
    }

    /**
     * Specifies that this character is 'ordinary': it removes any
     * significance as a word, comment, string, whitespace or number
     * character.  When encountered by the parser, it returns a ttype
     * equal to the character.
     * @param ch the character
     */
    public void ordinaryChar(int ch)
	{
		ctype[ch] = 0;
    }

    /**
     * Specifies that this character starts a single line comment.
     * @param ch the character
     */
    public void commentChar(int ch)
	{
		ctype[ch] = CT_COMMENT;
    }

    /**
     * Specifies that matching pairs of this character delimit String
     * constants.  When a String constant is recognized, ttype will be
     * the character that delimits the String, and sval will have
     * the body of the String.
     * @param ch the character
     */
    public void quoteChar(int ch)
	{
		ctype[ch] = CT_QUOTE;
    }

    /**
     * Specifies that numbers should be parsed.  This method accepts
     * double precision floating point numbers and returns a ttype of
     * CPascalCompiler.TT_NUMBER with the value in nval.
     */
    public void parseNumbers()
	{
		for (int i = '0'; i <= '9'; i++) ctype[i] |= CT_DIGIT;
    }

    /**
     * If the flag is true, end-of-lines are significant (CPascalCompiler.TT_EOL will
     * be returned by nexttoken).  If false, they will be treated
     * as whitespace.
     */
    public void eolIsSignificant(boolean flag)
	{
		eolIsSignificantP = flag;
    }


    /**
     * Examines a boolean to decide whether CPascalCompiler.TT_WORD tokens are
     * forced to be lower case.
     * @param fl the boolean flag
     */
    public void lowerCaseMode(boolean fl)
	{
		forceLower = fl;
    }

    /**
     * Parses a token from the input stream.  The return value is
     * the same as the value of ttype.  Typical clients of this
     * class first set up the syntax tables and then sit in a loop
     * calling nextToken to parse successive tokens until CPascalCompiler.TT_EOF
     * is returned.
     */
    public int nextToken() throws IOException, CCompilingError
	{
		if (pushedBack)
		{
			pushedBack = false;
			return ttype;
		}
		InputStream is = input;
		byte ct[] = ctype;
		int c = peekc;
		sval = null;
		if (c < 0) return ttype = CPascalCompiler.TT_EOF;
		int ctype = c < 256 ? ct[c] : CT_ALPHA;

		while ((ctype & CT_WHITESPACE) != 0)
		{
			if (c == '\r')
			{
				LINENO++;
				c = is.read();
				if (c == '\n') c = is.read();
				if (eolIsSignificantP)
				{
					peekc = c;
					return ttype = CPascalCompiler.TT_EOL;
				}
			}
			else
			{
				if (c == '\n')
				{
					LINENO++;
					if (eolIsSignificantP)
					{
						peekc = ' ';
						return ttype = CPascalCompiler.TT_EOL;
					}
				}
				c = is.read();
			}
			if (c < 0)
			return ttype = CPascalCompiler.TT_EOF;
			ctype = c < 256 ? ct[c] : CT_ALPHA;
		}

		if ((ctype & CT_DIGIT) != 0)
		{
			long v = 0;
			while (true)
			{
				if (c < '0' || c > '9') break;
				v = v * 10 + (c - '0');
				if (v > Integer.MAX_VALUE) throw new CIntOverflowError();
				c = is.read();
			}
			peekc = c;
			ival = (int)v;
			return ttype = CPascalCompiler.TT_INTNUM;
		}
		if ((ctype & CT_ALPHA) != 0)
		{
			int i = 0;
			do
			{
			if (i >= buf.length)
			{
				char nb[] = new char[buf.length * 2];
				System.arraycopy(buf, 0, nb, 0, buf.length);
				buf = nb;
			}
			buf[i++] = (char) c;
			c = is.read();
			ctype = c < 0 ? CT_WHITESPACE : c < 256 ? ct[c] : CT_ALPHA;
			} while ((ctype & (CT_ALPHA | CT_DIGIT)) != 0);
			peekc = c;
			sval = String.copyValueOf(buf, 0, i);
			if (forceLower)	sval = sval.toLowerCase();
			Integer item = (Integer)keyTable.get(sval);
			if (item == null) return ttype = CPascalCompiler.TT_WORD;
			return ttype = item.intValue();
		}
		if ((ctype & CT_COMMENT) != 0)
		{
			while ((c = is.read()) != '\n' && c != '\r' && c >= 0);
			peekc = c;
			return nextToken();
		}

		if (c == '{')
		{
			c = is.read();
			while (c != '}')
			{
				c = is.read();
				if (c == '\n') LINENO++;
				if (c < 0) throw new CExpectedError("}");
			}
			peekc = ' ';
			return nextToken();
		}
		byte []chToStrBuf = new byte[1];
		ttype = c;
		peekc = is.read();
		switch (c)
		{
		case ':':
			if (peekc == '=')
			{
				ttype = CPascalCompiler.TT_ASSIGN; peekc = ' ';
			}
			break;
		case '<':
			if (peekc == '=')
			{
				ttype = CPascalCompiler.TT_LE; peekc = ' ';
			}
			else if (peekc == '>')
			{
				ttype = CPascalCompiler.TT_NE; peekc = ' ';
			}
			break;
		case '>':
			if (peekc == '=')
			{
				ttype = CPascalCompiler.TT_GE; peekc = ' ';
			}
			break;

		case '.':
			if (peekc == '.')
			{
				ttype = CPascalCompiler.TT_DD; peekc = ' ';
			}
			break;
		case '\'':
			if (is.read() != '\'') throw new CExpectedError("'");
			ttype = CPascalCompiler.TT_CHAR;
			chToStrBuf[0] = (byte)peekc;
			sval = "" + (char)(peekc);
			peekc = ' ';
			break;
		}
		return ttype;
	}

	/**
	 * Get a new token from the current seat
	 *
	 * <TOKEN> -> <KEY WORD> | <IDENTIFIER> | <INT NUMBER> |
	 *				<COMMENT> | <OTHER SIGN>
	 *
	 * <KEY WORD> -> And | Array | Begin | Const | Do |
	 *				 Else | End | For | If | Mod | Not |
	 *				 Of	| Or | Procedure | Program | Record
	 *				 Then | To | Type | Var | While
	 *
	 *	<TT_ASSIGN> -> ":="
	 *	<TT_LE> -> "<="
	 *	<TT_GE> -> ">="
	 *	<TT_NE> -> "<>"
	 *	<TT_LE> -> ".."
	 *
     */
	int GetToken() throws IOException, CCompilingError
	{
		return nextToken();
    }

	/**
	 * Get a sign of t from the current seat
	 * If stream can't get it, throws the Expected Error
	 */
	void GetSign(int t) throws IOException, CCompilingError
	{
		if (GetToken() != t) throw new CExpectedError(TypeToString(t));
	}

	/**
	 * Get a identifer from the current seat
	 * If stream can't get it, throws the Expected Identifier Error
	 */
	void GetIdentifier() throws IOException, CCompilingError
	{
		if (GetToken() != CPascalCompiler.TT_WORD) throw new CExpectedIdentifierError();
	}

	/**
	 * Get a identifer or reserved word from the current seat
	 * If stream can't get it, throws the Expected Identifier Error
	 */
	int GetWord() throws IOException, CCompilingError
	{
		GetToken();
		if (ttype == CPascalCompiler.TT_WORD || ttype >= CPascalCompiler.TT_RESERVED) return ttype;
		throw new CExpectedIdentifierError();
	}

    /**
     * Pushes back a stream token.
     */
    public void pushBack() {
	pushedBack = true;
    }

    /** Return the current line number. */
    public int lineno() { return LINENO; }

	/** Return the string of the current token. */
	String TypeToString(int t)
	{
		switch (t)
		{
		case CPascalCompiler.TT_ASSIGN: return ":=";
		case CPascalCompiler.TT_LE:	return "<=";
		case CPascalCompiler.TT_GE: return ">=";
		case CPascalCompiler.TT_NE:	return "<>";
		case CPascalCompiler.TT_DD: return "..";
		case CPascalCompiler.TT_AND: return "and";
		case CPascalCompiler.TT_ARRAY: return "array";
		case CPascalCompiler.TT_BEGIN: return "begin";
		case CPascalCompiler.TT_CONST: return "const";
		case CPascalCompiler.TT_DIV: return "div";
		case CPascalCompiler.TT_DO: return "do";
		case CPascalCompiler.TT_DOWNTO: return "downto";
		case CPascalCompiler.TT_ELSE: return "else";
		case CPascalCompiler.TT_END: return "end";
		case CPascalCompiler.TT_FOR: return "for";
		case CPascalCompiler.TT_FUNCTION: return "function";
		case CPascalCompiler.TT_IF: return "if";
		case CPascalCompiler.TT_MOD: return "mod";
		case CPascalCompiler.TT_NOT: return "not";
		case CPascalCompiler.TT_OF: return "of";
		case CPascalCompiler.TT_OR: return "or";
		case CPascalCompiler.TT_PROCEDURE: return "procedure";
		case CPascalCompiler.TT_PROGRAM: return "program";
		case CPascalCompiler.TT_RECORD: return "record";
		case CPascalCompiler.TT_THEN: return "then";
		case CPascalCompiler.TT_TO: return "to";
		case CPascalCompiler.TT_TYPE: return "type";
		case CPascalCompiler.TT_VAR: return "var";
		case CPascalCompiler.TT_WHILE: return "while";
		}
		char buf[] = new char[1];
		buf[0] = (char)t;
		return new String(buf);
	}


    /**
     * Returns the String representation of the stream token.
	 *		for Debug
     */
    public String toString() {
	String ret;
	switch (ttype)
	{
		case CPascalCompiler.TT_EOF:
			ret = "EOF"; break;
		case CPascalCompiler.TT_EOL:
			ret = "EOL"; break;
		case CPascalCompiler.TT_WORD:
			ret = sval; break;
		case CPascalCompiler.TT_INTNUM:
			ret = "Integer[" + ival + "]";
			break;
		default:
			if (ttype >= CPascalCompiler.TT_RESERVED)
				ret = "Reseved key:" + sval;
			else
			{
				ret = "Sign[ " + TypeToString(ttype) + " ]";
			}
		}
		return ret + " , line " + lineno();
    }
	String GetString()
	{
		return sval;
	}

}
